﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace AntsCode.StorageClientExtensions.Helpers
{
    // Based on: http://huagati.blogspot.com/2009/10/code-sample-search-and-replace-in-linq.html
    internal static class ExpressionExtensions
    {
        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">Tree to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static Expression EvaluateParameter(this Expression expression, ParameterExpression parameter, object value)
        {
            Expression exp = null;
            Type expressionType = expression.GetType();
            if (expressionType == typeof(ParameterExpression) || expressionType.IsSubclassOf(typeof(ParameterExpression)))
            {
                exp = ((ParameterExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(MemberExpression) || expressionType.IsSubclassOf(typeof(MemberExpression)))
            {
                exp = ((MemberExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(MethodCallExpression) || expressionType.IsSubclassOf(typeof(MethodCallExpression)))
            {
                exp = ((MethodCallExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(NewExpression) || expressionType.IsSubclassOf(typeof(NewExpression)))
            {
                exp = ((NewExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(UnaryExpression) || expressionType.IsSubclassOf(typeof(UnaryExpression)))
            {
                exp = ((UnaryExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(ConstantExpression) || expressionType.IsSubclassOf(typeof(ConstantExpression)))
            {
                exp = ((ConstantExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(ConditionalExpression) || expressionType.IsSubclassOf(typeof(ConditionalExpression)))
            {
                exp = ((ConditionalExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(LambdaExpression) || expressionType.IsSubclassOf(typeof(LambdaExpression)))
            {
                exp = ((LambdaExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(MemberInitExpression) || expressionType.IsSubclassOf(typeof(MemberInitExpression)))
            {
                exp = ((MemberInitExpression)expression).EvaluateParameter(parameter, value);
            }
            else if (expressionType == typeof(BinaryExpression) || expressionType.IsSubclassOf(typeof(BinaryExpression)))
            {
                exp = ((BinaryExpression)expression).EvaluateParameter(parameter, value);
            }
            else
            {
                //did I forget some expression type? probably. this will take care of that... :)
                throw new NotImplementedException("Expression type " + expression.GetType().FullName + " not supported by this expression tree parser.");
            }
            return exp;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">LambdaExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static LambdaExpression EvaluateParameter(this LambdaExpression expression, ParameterExpression parameter, object value)
        {
            LambdaExpression lambdaExpression = null;
            lambdaExpression = Expression.Lambda(
                expression.Type,
                expression.Body.EvaluateParameter(parameter, value),
                (expression.Parameters != null) ? expression.Parameters.EvaluateParameter(parameter, value) : null
                );
            return lambdaExpression;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">BinaryExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static BinaryExpression EvaluateParameter(this BinaryExpression expression, ParameterExpression parameter, object value)
        {
            BinaryExpression binaryExp = null;
            binaryExp = Expression.MakeBinary(
                expression.NodeType,
                (expression.Left != null) ? expression.Left.EvaluateParameter(parameter, value) : null,
                (expression.Right != null) ? expression.Right.EvaluateParameter(parameter, value) : null,
                expression.IsLiftedToNull,
                expression.Method,
                (expression.Conversion != null) ? expression.Conversion.EvaluateParameter(parameter, value) : null
                );
            return binaryExp;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">ParameterExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static ParameterExpression EvaluateParameter(this ParameterExpression expression, ParameterExpression parameter, object value)
        {
            return expression;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">MemberExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static Expression EvaluateParameter(this MemberExpression expression, ParameterExpression parameter, object value)
        {
            if (expression.Expression == parameter)
            {
                var le = Expression.Lambda(expression, parameter);
                Delegate leCompiled = le.Compile();
                object memberValue = leCompiled.DynamicInvoke(value);

                if (memberValue == null)
                {
                    throw new NullNemberException("Member evaluated to null.");
                }

                return Expression.Constant(memberValue);
            }
            else
            {
                return Expression.MakeMemberAccess(
                    (expression.Expression != null) ? expression.Expression.EvaluateParameter(parameter, value) : null,
                    expression.Member);
            }
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">MemberInitExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static MemberInitExpression EvaluateParameter(this MemberInitExpression expression, ParameterExpression parameter, object value)
        {
            return Expression.MemberInit(
                (expression.NewExpression != null) ? expression.NewExpression.EvaluateParameter(parameter, value) : null,
                (expression.Bindings != null) ? expression.Bindings.EvaluateParameter(parameter, value) : null
                );
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">MethodCallExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static MethodCallExpression EvaluateParameter(this MethodCallExpression expression, ParameterExpression parameter, object value)
        {
            MethodCallExpression callExpression = null;
            callExpression = Expression.Call(
                (expression.Object != null) ? expression.Object.EvaluateParameter(parameter, value) : null,
                expression.Method,
                (expression.Arguments != null) ? expression.Arguments.EvaluateParameter(parameter, value) : null
                );
            return callExpression;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">NewExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static NewExpression EvaluateParameter(this NewExpression expression, ParameterExpression parameter, object value)
        {
            return Expression.New(
                expression.Constructor,
                (expression.Arguments != null) ? expression.Arguments.EvaluateParameter(parameter, value) : null,
                expression.Members);
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within a ReadonlyCollection of ParameterExpressions with another ParameterExpression, and return as an IEnumerable
        /// </summary>
        /// <param name="expression">ReadOnlyCollection&lt;ParameterExpression&gt; to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A IEnumerable returning the passed in set of ParameterExpressions, with occurences of parameter replaced with value</returns>
        public static IEnumerable<ParameterExpression> EvaluateParameter(this System.Collections.ObjectModel.ReadOnlyCollection<ParameterExpression> expressionArguments, ParameterExpression parameter, object value)
        {
            if (expressionArguments != null)
            {
                foreach (ParameterExpression argument in expressionArguments)
                {
                    if (argument != null)
                    {
                        yield return argument.EvaluateParameter(parameter, value);
                    }
                    else
                    {
                        yield return null;
                    }
                }
            }
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within a ReadonlyCollection of Expressions with another ParameterExpression, and return as an IEnumerable
        /// </summary>
        /// <param name="expression">ReadOnlyCollection&lt;Expression&gt; to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A IEnumerable returning the passed in set of Expressions, with occurences of parameter replaced with value</returns>
        public static IEnumerable<Expression> EvaluateParameter(this System.Collections.ObjectModel.ReadOnlyCollection<Expression> expressionArguments, ParameterExpression parameter, object value)
        {
            if (expressionArguments != null)
            {
                foreach (Expression argument in expressionArguments)
                {
                    if (argument != null)
                    {
                        yield return argument.EvaluateParameter(parameter, value);
                    }
                    else
                    {
                        yield return null;
                    }
                }
            }
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within a ReadonlyCollection of ElementInits with another ParameterExpression, and return as an IEnumerable
        /// </summary>
        /// <param name="expression">ReadOnlyCollection&lt;ElementInit&gt; to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A IEnumerable returning the passed in set of ParameterExpressions, with occurences of parameter replaced with value</returns>
        public static IEnumerable<ElementInit> EvaluateParameter(this System.Collections.ObjectModel.ReadOnlyCollection<ElementInit> elementInits, ParameterExpression parameter, object value)
        {
            if (elementInits != null)
            {
                foreach (ElementInit elementInit in elementInits)
                {
                    if (elementInit != null)
                    {
                        yield return Expression.ElementInit(elementInit.AddMethod, elementInit.Arguments.EvaluateParameter(parameter, value));
                    }
                    else
                    {
                        yield return null;
                    }
                }
            }
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within a ReadonlyCollection of MemberBindings with another ParameterExpression, and return as an IEnumerable
        /// </summary>
        /// <param name="expression">ReadOnlyCollection&lt;MemberBinding&gt; to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A IEnumerable returning the passed in set of ParameterExpressions, with occurences of parameter replaced with value</returns>
        public static IEnumerable<MemberBinding> EvaluateParameter(this System.Collections.ObjectModel.ReadOnlyCollection<MemberBinding> memberBindings, ParameterExpression parameter, object value)
        {
            if (memberBindings != null)
            {
                foreach (MemberBinding binding in memberBindings)
                {
                    if (binding != null)
                    {
                        switch (binding.BindingType)
                        {
                            case MemberBindingType.Assignment:
                                MemberAssignment memberAssignment = (MemberAssignment)binding;
                                yield return Expression.Bind(binding.Member, memberAssignment.Expression.EvaluateParameter(parameter, value));
                                break;
                            case MemberBindingType.ListBinding:
                                MemberListBinding listBinding = (MemberListBinding)binding;
                                yield return Expression.ListBind(binding.Member, listBinding.Initializers.EvaluateParameter(parameter, value));
                                break;
                            case MemberBindingType.MemberBinding:
                                MemberMemberBinding memberMemberBinding = (MemberMemberBinding)binding;
                                yield return Expression.MemberBind(binding.Member, memberMemberBinding.Bindings.EvaluateParameter(parameter, value));
                                break;
                        }
                    }
                    else
                    {
                        yield return null;
                    }
                }
            }
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">UnaryExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static UnaryExpression EvaluateParameter(this UnaryExpression expression, ParameterExpression parameter, object value)
        {
            return Expression.MakeUnary(
                expression.NodeType,
                (expression.Operand != null) ? expression.Operand.EvaluateParameter(parameter, value) : null,
                expression.Type,
                expression.Method);
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree. Note: this version of EvaluateParameter exists just for conformity - there can't be a parameter expression hiding under a constant expression so this could really be skipped.
        /// </summary>
        /// <param name="expression">ConstantExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static ConstantExpression EvaluateParameter(this ConstantExpression expression, ParameterExpression parameter, object value)
        {
            //return Expression.Constant(expression.Value, expression.Type);
            return expression;
        }

        /// <summary>
        /// Replace all occurences of a ParameterExpression within an expression tree with another ParameterExpression, and return a cloned tree
        /// </summary>
        /// <param name="expression">ConditionalExpression to replace parameters in</param>
        /// <param name="parameter">Parameter to replace</param>
        /// <param name="value">Parameter to use as replacement</param>
        /// <returns>A cloned expression tree with all occurences of parameter replaced with value</returns>
        public static ConditionalExpression EvaluateParameter(this ConditionalExpression expression, ParameterExpression parameter, object value)
        {
            return Expression.Condition(
                (expression.Test != null) ? expression.Test.EvaluateParameter(parameter, value) : null,
                (expression.IfTrue != null) ? expression.IfTrue.EvaluateParameter(parameter, value) : null,
                (expression.IfFalse != null) ? expression.IfFalse.EvaluateParameter(parameter, value) : null
                );
        }
    }
}